package com.dliyun.plugin.filemanager.controllers;

import com.dliyun.platform.common.DwzJSON;
import com.dliyun.platform.common.exception.MessageException;
import com.dliyun.platform.common.exception.NoFoundException;
import com.dliyun.platform.common.oauth.Permission;
import com.dliyun.platform.common.service.SysConfigService;
import com.dliyun.platform.common.utils.AesSecurityHex;
import com.dliyun.plugin.filemanager.model.FileInfo;
import com.dliyun.plugin.filemanager.utils.FileUtil;
import com.dliyun.plugin.filemanager.utils.ZipUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.zip.ZipOutputStream;

/**
 * @author shaofan
 */
@Slf4j
@Controller
@RequestMapping(value = "/filemanager")
public class FileManagerController {

    @Autowired
    private SysConfigService sysConfigService;

    private final String AES_KEY = "889FA30C9219CB6749EA5E0780C6CC08";


    private String getFileRoot() {
        return this.sysConfigService.getStringValue("filemanager", "filemanager", "file.root");
    }

    /**
     * 展示文件列表
     */
    @RequestMapping("/index.htm")
    @Permission(pluginKey = "filemanager", moduleKey = "filemanager", authority = {"file.manager.find"})
    public String index(String path, ModelMap modelMap) throws Exception {


        if (StringUtils.isBlank(path) || path.equals(File.separator) || path.equals("/")) {
            path = "";
        } else {
            path = AesSecurityHex.decrypt(path, AES_KEY);
        }
        String fileTypeExts = this.sysConfigService.getStringValue("filemanager", "filemanager", "file.type.exts");
        String rootPath = this.getFileRoot();
        if (StringUtils.isBlank(fileTypeExts) || StringUtils.isBlank(rootPath)) {
            throw new MessageException("您还没有配置根目录与允许上传的文件类型，请先到系统配置是完成配置后再使用");
        }

        Path relativePath = Paths.get(path);
        Iterator<Path> relativePathIterator = relativePath.iterator();

        List<FileInfo> listPaths = new ArrayList<>();
        final Path[] tmp = {Paths.get("")};
        relativePathIterator.forEachRemaining((Path p) -> {
            try {
                if (!p.toString().equals("..") && StringUtils.isNotBlank(p.toString()) && !p.toString().equals(File.separator)) {
                    FileInfo pi = new FileInfo();
                    pi.setName(p.getFileName().toString());
                    tmp[0] = Paths.get(tmp[0].toString(), pi.getName());
                    pi.setPath(AesSecurityHex.encrypt(tmp[0].toString(), AES_KEY));
                    listPaths.add(pi);
                }
            } catch (Exception e) {
                log.error("", e);
            }
        });

        modelMap.addAttribute("listPaths", listPaths);

        Path parentPath = relativePath.getParent();
        if (parentPath == null && !listPaths.isEmpty()) {
            parentPath = Paths.get(File.separator);
        }
        modelMap.addAttribute("parentPath", parentPath);
        Path absolutePath = Paths.get(this.getFileRoot());
        for (FileInfo pi : listPaths) {
            absolutePath = Paths.get(absolutePath.toString(), pi.getName());
        }

        List<FileInfo> listItems = new ArrayList<>();
        DirectoryStream<Path> directoryStream = Files.newDirectoryStream(absolutePath);
        for (Path pathObj : directoryStream) {
            // 获取文件基本属性
            BasicFileAttributes attrs = Files.readAttributes(pathObj, BasicFileAttributes.class);

            // 封装返回JSON数据
            FileInfo fileInfo = new FileInfo();
            fileInfo.setName(pathObj.getFileName().toString());
            fileInfo.setPermissions(FileUtil.getPermissions(pathObj)); // 文件权限
            fileInfo.setLastModifiedTime(new Date(attrs.lastModifiedTime().toMillis()));
            fileInfo.setSize(attrs.size());
            fileInfo.setDirectory(attrs.isDirectory());
            Path p = Paths.get(relativePath.toString(), fileInfo.getName());
            log.info(p.toString());

            fileInfo.setPath(AesSecurityHex.encrypt(p.toString(), AES_KEY));
            String mimeType = URLConnection.guessContentTypeFromName(pathObj.toString());
            fileInfo.setMimeType(mimeType);
            fileInfo.setCanEdit(this.checkCanEdit(mimeType));
            listItems.add(fileInfo);
        }
        modelMap.addAttribute("listItems", listItems);
        modelMap.addAttribute("relativePath", AesSecurityHex.encrypt(relativePath.toString(), AES_KEY));
        modelMap.addAttribute("fileTypeExts", fileTypeExts);
        return "filemanager/index";
    }


    /**
     * 文件编辑
     */
    @RequestMapping("/edit.htm")
    @Permission(pluginKey = "filemanager", moduleKey = "filemanager", authority = {"file.manager.preview"})
    public String edit(String path, ModelMap modelMap) throws Exception {
        if (StringUtils.isBlank(path) || path.equals(File.separator) || path.equals("/")) {
            path = "";
        } else {
            path = AesSecurityHex.decrypt(path, AES_KEY);
        }

        File srcFile = new File(this.getFileRoot(), path);
        if (!srcFile.exists()) {
            throw new MessageException("文件不存在");
        }
        if (srcFile.isDirectory()) {
            throw new MessageException("无法查看目录文件");
        }

        String fileName = srcFile.getName();
        String extension = "txt";
        try {
            extension = fileName.substring(fileName.lastIndexOf(".")).replaceAll("\\.", "");
        } catch (Exception e) {
            log.warn("can not find extension", e);
        }
        String fileContent = FileUtils.readFileToString(srcFile, "UTF-8");
        modelMap.addAttribute("fileContent", fileContent);
        modelMap.addAttribute("relativePath", AesSecurityHex.encrypt(path, AES_KEY));
        modelMap.addAttribute("fileName", fileName);
        modelMap.addAttribute("extension", extension);
        return "filemanager/edit";
    }


    /**
     * 文件保存
     */
    @ResponseBody
    @RequestMapping("/save.ajax")
    @Permission(pluginKey = "filemanager", moduleKey = "filemanager", authority = {"file.manager.preview"})
    public DwzJSON save(String path, String fileContent) throws Exception {
        if (StringUtils.isBlank(path) || path.equals(File.separator) || path.equals("/")) {
            path = "";
        } else {
            path = AesSecurityHex.decrypt(path, AES_KEY);
        }

        File srcFile = new File(this.getFileRoot(), path);
        if (!srcFile.exists()) {
            return DwzJSON.body(DwzJSON.StatusCode.error).setMessage("文件不存在");
        }
        if (srcFile.isDirectory()) {
            return DwzJSON.body(DwzJSON.StatusCode.error).setMessage("目录文件无法编辑");
        }

        FileWriter fileWriter = new FileWriter(srcFile, false);
        fileWriter.write(fileContent);
        fileWriter.close();
        return DwzJSON.body(DwzJSON.StatusCode.success).setMessage("保存成功");

    }

    /**
     * 文件下载
     */
    @RequestMapping("/download.htm")
    @Permission(pluginKey = "filemanager", moduleKey = "filemanager", authority = {"file.manager.preview"})
    public ResponseEntity<byte[]> download(String path, HttpServletRequest request) throws Exception {
        if (StringUtils.isBlank(path) || path.equals(File.separator) || path.equals("/")) {
            path = "";
        } else {
            path = AesSecurityHex.decrypt(path, AES_KEY);
        }

        File srcFile = new File(this.getFileRoot(), path);
        if (!srcFile.exists()) {
            throw new NoFoundException("文件不存在");
        }
        if (srcFile.isDirectory()) {
            throw new NoFoundException("无法查看目录文件");
        }

        HttpHeaders headers = new HttpHeaders();// http协议
        String userAgent = request.getHeader("User-Agent");
        String fileName = srcFile.getName();
        if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {
            fileName = URLEncoder.encode(fileName, "UTF-8");
        } else {
            fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
        }
        headers.setContentDispositionFormData("attachment", fileName);// 默认下载文件名为原始文件名
        String mimeType = URLConnection.guessContentTypeFromName(srcFile.getName());
        if (StringUtils.isBlank(mimeType)) {
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);// 媒体流下载
        } else {
            headers.setContentType(MediaType.valueOf(mimeType));// 媒体流下载
        }
        return new ResponseEntity<>(FileUtils.readFileToByteArray(srcFile), headers, HttpStatus.CREATED);
    }


    /**
     * 文件上传
     */
    @ResponseBody
    @RequestMapping("/upload.ajax")
    @Permission(pluginKey = "filemanager", moduleKey = "filemanager", authority = {"file.manager.upload"})
    public Object upload(@RequestParam(value = "uploadFileData") MultipartFile uploadFileData, String path, String fileName, long lastModifiedDate) throws IOException {
        if (StringUtils.isBlank(path) || path.equals(File.separator) || path.equals("/")) {
            path = "";
        } else {
            path = AesSecurityHex.decrypt(path, AES_KEY);
        }

        Path relativePath = Paths.get(path);

        Iterator<Path> relativePathIterator = relativePath.iterator();

        final Path[] tmp = {Paths.get(this.getFileRoot())};
        relativePathIterator.forEachRemaining((Path p) -> {
            if (!p.toString().equals("..") && StringUtils.isNotBlank(p.toString()) && !p.toString().equals(File.separator)) {
                tmp[0] = Paths.get(tmp[0].toString(), p.getFileName().toString());
            }
        });
        Path absolutePath = Paths.get(tmp[0].toString(), fileName);
        File saveFile = new File(absolutePath.toString());
        boolean s = saveFile.setLastModified(lastModifiedDate);
        uploadFileData.transferTo(saveFile);
        return DwzJSON.body(DwzJSON.StatusCode.success).setMessage(String.format("%s上传成功了", fileName)).setTabid("filemanager", "filemanager", "filemanager");
    }


    /**
     * 删除文件或目录
     */
    @ResponseBody
    @RequestMapping("/delete.ajax")
    @Permission(pluginKey = "filemanager", moduleKey = "filemanager", authority = {"file.manager.delete"})
    public Object delete(String path, String[] fileNames) {
        if (StringUtils.isBlank(path) || path.equals(File.separator) || path.equals("/")) {
            path = "";
        } else {
            path = AesSecurityHex.decrypt(path, AES_KEY);
        }

        for (String fileName : fileNames) {
            Path absolutePath = Paths.get(this.getFileRoot(), path, fileName);
            File srcFile = new File(absolutePath.toString());
            if (!FileUtils.deleteQuietly(srcFile)) {
                log.error("删除失败,{}", srcFile.getAbsolutePath());
            }
        }
        return DwzJSON.body(DwzJSON.StatusCode.success).setMessage("删除文件成功").setTabid("filemanager", "filemanager", "filemanager");
    }

    /**
     * 创建目录
     */
    @ResponseBody
    @RequestMapping("/mkdir.ajax")
    @Permission(pluginKey = "filemanager", moduleKey = "filemanager", authority = {"file.manager.delete"})
    public Object mkdir(String path, String dirName) throws MessageException {
        if (StringUtils.isBlank(path) || path.equals(File.separator) || path.equals("/")) {
            path = "";
        } else {
            path = AesSecurityHex.decrypt(path, AES_KEY);
        }

        Path absolutePath = Paths.get(this.getFileRoot(), path, dirName);
        File newDir = new File(absolutePath.toString());
        if (!newDir.mkdir()) {
            throw new MessageException("不能创建目录: " + absolutePath);
        }
        return DwzJSON.body(DwzJSON.StatusCode.success).setMessage("创建目录成功").setCloseCurrent(true).setTabid("filemanager", "filemanager", "filemanager");
    }

    /**
     * 文件压缩
     */
    @ResponseBody
    @RequestMapping("/compress.ajax")
    @Permission(pluginKey = "filemanager", moduleKey = "filemanager", authority = {"file.manager.compress"})
    public Object compress(String[] files, String path, String compressedFileName) throws Exception {
        if (StringUtils.isBlank(path) || path.equals(File.separator) || path.equals("/")) {
            path = "";
        } else {
            path = AesSecurityHex.decrypt(path, AES_KEY);
        }
        Path absolutePath = Paths.get(this.getFileRoot(), path);
        List<File> listFiles = new ArrayList<>();
        for (String file : files) {
            File f = new File(absolutePath.toString(), file);
            listFiles.add(f);
        }

        File zipFile = new File(absolutePath.toString(), String.format("%s%s%s", compressedFileName, ".", "zip"));

        try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile))) {
            ZipUtils.zipFiles(out, "", listFiles.toArray(new File[0]));
        }
        return DwzJSON.body(DwzJSON.StatusCode.success).setMessage("压缩文件成功").setCloseCurrent(true).setTabid("filemanager", "filemanager", "filemanager");
    }

    private boolean checkCanEdit(String mimeType) {
        if (StringUtils.isBlank(mimeType)) {
            return false;
        }
        return mimeType.startsWith("text")
                || mimeType.endsWith("x-sh")
                || mimeType.endsWith("x-shar")
                || mimeType.endsWith("x-javascript");
    }
}
